#include <string.h>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include "sm_trace.h"
#include "itc.h"
#include "app_proxy.h"
#include "common.h"


#define CAS_SESSION_COUNT 	8
typedef struct
{
	unsigned char 		m_isUsed;
	short 				m_ssnb;
	unsigned char 		m_isCaInfoValid;
	unsigned char 		m_systemIdCount;
	unsigned short* 	m_systemIdArray;
}cas_session_table_t;

static cas_session_table_t s_cas_tab_session[CAS_SESSION_COUNT];
static unsigned char *p_capmt_databuf = NULL;
static int	s_capmt_len = 0;

int cas_init()
{
	int i;

	SM_TRACE_ENTRY(TRMOD_CAS, "cas_init() - entry\r\n");

	for(i=0;i<CAS_SESSION_COUNT;i++)
	{
		s_cas_tab_session[i].m_isUsed=0;
	}

	SM_TRACE_RETURN(TRMOD_CAS, "cas_init() - return 0\r\n");
	return 0;
}

int cas_on_message(xmsg_t* pXmsg)
{
	unsigned short	ssnb;
	unsigned char	tcid = 0;
	int 			i;
	xmsg_t			xmsg;

	SM_ASSERT(pXmsg);

	SM_TRACE_ENTRY(TRMOD_CAS, "cas_on_message() - entry,pXmsg=0x%08X\r\n", pXmsg);

	SM_TRACE_INFO(TRMOD_CAS, "cas_on_message() - %s(0x%02X) received\r\n",
				XMSG_NAME(pXmsg->m_type),pXmsg->m_type);

	switch(pXmsg->m_type)
	{
	case APP_OPEN_SS_REQ:
		ssnb = pXmsg->m_bd.m_app_open_ss_req.m_ssnb;
		tcid = pXmsg->m_bd.m_app_open_ss_req.m_tcid;
		SM_TRACE_INFO(TRMOD_CAS, "cas_on_message() - cas_on_message received, ssnb=%d\r\n", ssnb);
		for(i=0;i<CAS_SESSION_COUNT;i++)
		{
			if(!s_cas_tab_session[i].m_isUsed)
				break;
		}
		memset(&xmsg, 0x00, sizeof(xmsg));
		xmsg.m_sndrcver = (RM << 4) | RM;
		xmsg.m_type  = APP_OPEN_SS_RSP;
		xmsg.m_bd.m_app_open_ss_rsp.m_tcid = tcid;
		xmsg.m_bd.m_app_open_ss_rsp.m_ssnb = ssnb;
		xmsg.m_bd.m_app_open_ss_rsp.m_ressId = pXmsg->m_bd.m_app_open_ss_req.m_ressId;
		if(i < CAS_SESSION_COUNT)
		{
			//item has been used, but ca_info not valid yet.
			s_cas_tab_session[i].m_isUsed = 1;
			s_cas_tab_session[i].m_ssnb=ssnb;
			s_cas_tab_session[i].m_isCaInfoValid = 0;
			xmsg.m_bd.m_app_open_ss_rsp.m_status = SS_OK;
		}
		else
		{
			SM_TRACE_INFO(TRMOD_CAS,"cas_on_message() - cas session table full\r\n");
			xmsg.m_bd.m_app_open_ss_rsp.m_status=SS_NOTAVAILABLE;
		}

		SM_TRACE_INFO(TRMOD_CAS, "cas_on_message() - will send APP_OPEN_SS_RSP to PROXY\r\n");
		ITC_SendXmsg(&xmsg);

		break;

	case APP_SS_OPENED:
		SM_TRACE_INFO(TRMOD_CAS, "cas_on_message() - APP_SS_OPENED received\r\n");
		ssnb=pXmsg->m_bd.m_app_ss_opened.m_ssnb;

		SM_TRACE_INFO(TRMOD_CAS,"cas_on_message() - send ca info enquiry, ssnb: %d\r\n", ssnb);
		send_app_apdu_to_proxy(ssnb, CI_APDU_TAG_CA_INFO_ENQ, NULL, 0); // ca info enquiry
		if(p_capmt_databuf != NULL && s_capmt_len != 0)
		{
			SM_TRACE_ERROR(TRMOD_CAS,"cas_on_message() - send ca_pmt, ssnb: %d\r\n", ssnb);
			send_app_apdu_to_proxy(ssnb, CI_APDU_TAG_CA_PMT, p_capmt_databuf, s_capmt_len); 
		}
		break;

	case APP_SS_BROKEN:
		ssnb=pXmsg->m_bd.m_app_ss_broken.m_ssnb;
		for(i=0;i<CAS_SESSION_COUNT;i++)
		{
			if((s_cas_tab_session[i].m_isUsed)
				&&(s_cas_tab_session[i].m_ssnb==ssnb))
			{
				s_cas_tab_session[i].m_isUsed=0;
				if(s_cas_tab_session[i].m_systemIdCount>32)
				{
					free(s_cas_tab_session[i].m_systemIdArray);
				}
			}
		}
		break;

	case APP_APDU:
	{
		unsigned long 	apduTag;
		unsigned long 	bodyLen;
		long 			totalLen;
		unsigned char* 	pbBody = NULL;
		unsigned char* 	pbApdu = NULL;

		totalLen=pXmsg->m_bd.m_app_apdu.m_apduLen;
		ssnb=pXmsg->m_bd.m_app_apdu.m_ssnb;
		pbApdu = pXmsg->m_bd.m_app_apdu.m_pbApdu;
		for(;totalLen>0;)
		{
			pbBody=decode_apdu(pbApdu, &apduTag, &bodyLen);
			totalLen=totalLen-bodyLen-((unsigned long)pbBody-(unsigned long)pbApdu);
			if(totalLen<0)
			{
				SM_TRACE_INFO(TRMOD_CAS,"cas_on_message() - apdu error\r\n");
				break;
			}
			pbApdu = pbBody+bodyLen; //point to the next apdu

			switch(apduTag)
			{
			case CI_APDU_TAG_CA_INFO: //ca_info
				SM_TRACE_INFO(TRMOD_CAS,"cas_on_message() - ca_info arrived, sessnb: %d\r\n", ssnb);
				if(bodyLen%2)
					SM_TRACE_ERROR(TRMOD_CAS,"cas_on_message() - apdu error\r\n");

				for(i=0;i<CAS_SESSION_COUNT;i++)
				{
					if(s_cas_tab_session[i].m_isUsed && (s_cas_tab_session[i].m_ssnb==ssnb))
						break;
				}
				if (i >= CAS_SESSION_COUNT)
				{
					SM_TRACE_ERROR(TRMOD_CAS,"cas_on_message() - not found ssnb=%d\r\n", ssnb);
					break;
				}

				s_cas_tab_session[i].m_systemIdArray=(unsigned short *)ITC_DynAlloc(bodyLen);
				if(NULL==s_cas_tab_session[i].m_systemIdArray)
				{
					SM_TRACE_ERROR(TRMOD_CAS,"cas_on_message() - memory out\r\n");
					break;
				}
				s_cas_tab_session[i].m_isCaInfoValid=1;

				s_cas_tab_session[i].m_systemIdCount = 0;
				for (; bodyLen >=  2; )
				{
					s_cas_tab_session[i].m_systemIdArray[s_cas_tab_session[i].m_systemIdCount] = asnReadInt16(pbBody);
					SM_TRACE_INFO(TRMOD_CAS,"cas_on_message() - system_id%d=0x%04X\r\n",
								s_cas_tab_session[i].m_systemIdCount,
								s_cas_tab_session[i].m_systemIdArray[s_cas_tab_session[i].m_systemIdCount]);
					s_cas_tab_session[i].m_systemIdCount++;
					pbBody+=2;
					bodyLen-=2;
				}
				//cas_info_arrived();  //notify application
				break;

			case CI_APDU_TAG_PMT_REPLY://ca_pmt_reply
				SM_TRACE_INFO(TRMOD_CAS,"cas_on_message() - ca pmt reply recevied\r\n");
				break;
			default:
				SM_TRACE_ERROR(TRMOD_CAS,"cas_on_message() - unknown apdutag=0x%06X\r\n", apduTag);
				break;
			}
		} //end for

		break;
	}

	default:
		SM_TRACE_ERROR(TRMOD_CAS,"cas_on_message() - unkown mess type=0x%02X\r\n", pXmsg->m_type);
		break;
	}

	SM_TRACE_RETURN(TRMOD_CAS, "cas_on_message() - return\r\n");
	return 0;
}


static int format_ca_pmt(unsigned char 	ca_pmt_list_cmd, unsigned char ca_pmt_cmd,
						unsigned char * buffer,
						int length,
						unsigned char *p_ca_pmt_result,
						unsigned int *result_length)
{
//	get a ca_pmt apdu according to the parameter the caller provide.
//	if success, p_ca_pmt_result points to the addres of the first bytes of the apdu,
//	result_length points to the address of total length of the apdu,
//	and the caller is responsible to free the memory containing the apdu.
//	return value:
//		0	success
//		-1 	parameter error
//		-2	content error
//		-3	not enough memory

	int i, j, k;
	unsigned char* p_capmt_body;
	int ca_pmt_index, buffer_index;
	int descriptor_length;
	int descriptor_length_index;

	SM_TRACE_ENTRY(TRMOD_CAS, "format_ca_pmt() - entry,ca_pmt_list_cmd=0x%02X\r\n",
					ca_pmt_list_cmd);

	*result_length=0;

	if(NULL==buffer)
	{
		return -1;//parameter error
	}
	if(length<5)//length can't be less than 6 bytes
	{
		return -1;
	}

	p_capmt_body=(unsigned char *)ITC_DynAlloc((unsigned short)(length+1024));//more 1024 to contain list management and cmd id.
	if(NULL==p_capmt_body)
	{
		return -3;//not enough memory
	}

	p_capmt_body[0]=ca_pmt_list_cmd;//list management: only
	buffer_index = 3; // skip table_id and section_len
	//program number;
	p_capmt_body[1]=buffer[buffer_index++];
	p_capmt_body[2]=buffer[buffer_index++];
	//version number and current next indicator
	p_capmt_body[3]=buffer[buffer_index++];
	//program info length
	buffer_index += 4; // skip section number and others
	p_capmt_body[4]=buffer[buffer_index++];// 3
	p_capmt_body[5]=buffer[buffer_index++];// 4

	k=(p_capmt_body[4]&0xf)*256+p_capmt_body[5];//program info length
	SM_TRACE_NOTIFY(TRMOD_CAS,"format_ca_pmt() - program info length: %d\r\n", k);
	
	ca_pmt_index=6;//buffer[buffer_index] is going to be written to p_capmt_body[ca_pmt_index]
	descriptor_length=0;//record the length of the descriptor	

	if(k>0)
	{
		if(buffer_index+k >= length)
		{
			goto CONTENT_ERROR;
		}
		p_capmt_body[ca_pmt_index++]=ca_pmt_cmd;//ok_descrambling
		memcpy(p_capmt_body+ca_pmt_index, buffer+buffer_index, k);
		ca_pmt_index += k;
		buffer_index += k;

		descriptor_length=k+1;
		
		//update program_info_length
		descriptor_length_index=4;
		p_capmt_body[descriptor_length_index]=((descriptor_length>>8)&0xff)|0xf0;
		p_capmt_body[descriptor_length_index+1]=descriptor_length&0xff;
	}

	for(;;)
	{
		p_capmt_body[ca_pmt_index++]=buffer[buffer_index++];//stream type

		p_capmt_body[ca_pmt_index++]=buffer[buffer_index++];
		p_capmt_body[ca_pmt_index++]=buffer[buffer_index++];//element stream id

		descriptor_length=0;//record the length of the descriptor
		descriptor_length_index=ca_pmt_index;
		
		p_capmt_body[ca_pmt_index++]=buffer[buffer_index++];
		p_capmt_body[ca_pmt_index++]=buffer[buffer_index++];//es info length
		if(buffer_index >= length)
			break;
		
		j=(buffer[buffer_index-2]&0xf)*256+buffer[buffer_index-1];//es info length
		SM_TRACE_NOTIFY(TRMOD_CAS,"format_ca_pmt() - es info length: %d\r\n", j);
		
		if(j>0)
		{
			if(buffer_index+j >= length)
				break;
			p_capmt_body[ca_pmt_index++]=ca_pmt_cmd;//ok_descrambling
			memcpy(p_capmt_body+ca_pmt_index, buffer+buffer_index, j);
			ca_pmt_index += j;
			buffer_index += j;

			descriptor_length=j+1;
			
			//update es info length
			p_capmt_body[descriptor_length_index]=((descriptor_length>>8)&0xff)|0xf0;
			p_capmt_body[descriptor_length_index+1]=descriptor_length&0xff;
		}

		if(buffer_index+4 >= length) // CRC
			break;
	}//content filling finished

	*result_length = ca_pmt_index;
	memcpy(p_ca_pmt_result, p_capmt_body, ca_pmt_index);
	ITC_DynFree(p_capmt_body);
	
	SM_TRACE_RETURN(TRMOD_CAS, "format_ca_pmt() - return OK\r\n");
	return 0;

CONTENT_ERROR:
	ITC_DynFree(p_capmt_body);

	SM_TRACE_RETURN(TRMOD_CAS, "format_ca_pmt() - return ERROR\r\n");
	return -2;
}


int cas_send_program_pmt(unsigned short ca_system_id, unsigned char * buffer, int length)
{
//  this function is interface to other parts of host software
//	this function maybe think about to stop the current ca application
//	if an other new ca application is used for the new program.
//	return value:
//		0	success
//		-1 	parameter error
//		-2	content error
//		-3	not enough memory
//		-4	no ca application
//		-5  send message error
	int 			i;
	unsigned char* 	p_capmt_body;
	unsigned short 	ssnb = 0;
	int				capmtLen = 0;
	const unsigned char ca_pmt_list_cmd_only = 0x03;
	const unsigned char ca_pmt_cmd_id_ok_descrambling = 0x01;

	SM_TRACE_ENTRY(TRMOD_CAS, "cas_send_program_pmt() - entry\r\n");

	SM_TRACE_BYTES(TRMOD_CAS, TRLEV_NOTIFY, buffer, length, "cas_send_program_pmt() - pmt:");

	if((NULL==buffer)||(length<=0))
		return -1;

	p_capmt_body = (unsigned char *)ITC_DynAlloc((unsigned short)(length+1024));//more 1024 to contain list management and cmd id.
	if(NULL==p_capmt_body)
	{
		return -1;//not enough memory
	}

	i=format_ca_pmt(ca_pmt_list_cmd_only, ca_pmt_cmd_id_ok_descrambling, buffer, length, p_capmt_body, &capmtLen);
	if(i)
	{
		SM_TRACE_ERROR(TRMOD_CAS,"cas_send_program_pmt() - i = %d\r\n", i);
		ITC_DynFree(p_capmt_body);
		return -1;
	}

	SM_TRACE_BYTES(TRMOD_CAS, TRLEV_NOTIFY, p_capmt_body, capmtLen, "cas_send_program_pmt() - capmt without tag:");
	for(i=0;i<CAS_SESSION_COUNT;i++)
	{
		if(s_cas_tab_session[i].m_isUsed)
		{
			ssnb=s_cas_tab_session[i].m_ssnb;
			SM_TRACE_INFO(TRMOD_CAS,"cas_send_program_pmt() - send capmt, ssnb: %d\r\n", ssnb);
			send_app_apdu_to_proxy(ssnb, CI_APDU_TAG_CA_PMT, p_capmt_body, capmtLen); 
		}
		else
		{
			SM_TRACE_NOTIFY(TRMOD_CAS,"cas_send_program_pmt() - found cas session not open\r\n");
		}
	}
	
	if(s_capmt_len < capmtLen)//reallocate memory
	{
		if(p_capmt_databuf != NULL)
		{
			ITC_DynFree(p_capmt_databuf);
			p_capmt_databuf = NULL;
		}
		p_capmt_databuf = (unsigned char *)ITC_DynAlloc(capmtLen);
	}

	if(p_capmt_databuf)
	{
		memcpy(p_capmt_databuf, p_capmt_body, capmtLen);
		s_capmt_len = capmtLen;
	}
	else
	{
		SM_TRACE_ERROR(TRMOD_CAS,"cas_send_program_pmt() - allocate memory failed, size = %d\r\n", capmtLen);
		s_capmt_len = 0;
	}
	
	ITC_DynFree(p_capmt_body);

	SM_TRACE_RETURN(TRMOD_CAS, "cas_send_program_pmt() - return\r\n");
	return 0;
}

static int cas_capmt_body_remove_descriptor(unsigned char* pu8CapmtBody, unsigned int u32Len, unsigned char* pu8Result, unsigned int* pu32ResultLen)
{
	unsigned int u32Index, u32SourceIndex;
	unsigned int u32DescriptorLength;
	unsigned int u32DescriptorLengthIndex;
	
	SM_TRACE_ENTRY(TRMOD_CAS, "cas_capmt_body_remove_descriptor() - entry, pu8CapmtBody = %p, u32Len = %d, pu8Result = %p, pu32ResultLen = %p\r\n",
		pu8CapmtBody, u32Len, pu8Result, pu32ResultLen);
	u32Index = 0;
	u32SourceIndex = 0;
	pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++];

	pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++];
	pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++];

	pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++];

	pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex]&0xF;
	pu8Result[u32Index++] = 0;
	u32DescriptorLength = (pu8CapmtBody[u32SourceIndex++]&0xF);
	u32DescriptorLength = (u32DescriptorLength<<8)|pu8CapmtBody[u32SourceIndex++];
	u32SourceIndex += u32DescriptorLength;
	SM_TRACE_NOTIFY(TRMOD_CAS,"cas_capmt_body_remove_descriptor() - u32DescriptorLength = %d\r\n", u32DescriptorLength);

	while(u32SourceIndex < u32Len)
	{
		SM_TRACE_NOTIFY(TRMOD_CAS,"cas_capmt_body_remove_descriptor() - found one es type = %d\r\n", pu8CapmtBody[u32SourceIndex]);
		pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++]; // stream type
		
		pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++]; //es pid
		pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex++];

		pu8Result[u32Index++] = pu8CapmtBody[u32SourceIndex]&0xF;
		pu8Result[u32Index++] = 0;
		u32DescriptorLength = (pu8CapmtBody[u32SourceIndex++]&0xF);
		u32DescriptorLength = (u32DescriptorLength<<8)|pu8CapmtBody[u32SourceIndex++];
		u32SourceIndex += u32DescriptorLength;
	}

	*pu32ResultLen = u32Index;
	SM_TRACE_NOTIFY(TRMOD_CAS,"cas_capmt_body_remove_descriptor() - delete descriptor OK, *pu32ResultLen = %d\r\n", *pu32ResultLen);
	
	SM_TRACE_RETURN(TRMOD_CAS, "cas_capmt_body_remove_descriptor() - return\r\n");
}

int cas_stop_discrambling()
{
	int 			i;
	unsigned char* pu8Temp;
	unsigned short ssnb = 0;
	unsigned int capmtLen = 0;

	SM_TRACE_ENTRY(TRMOD_CAS, "cas_stop_discrambling() - entry\r\n");
	if(s_capmt_len > 0)
	{
		pu8Temp = (unsigned char *)ITC_DynAlloc(s_capmt_len);
		if(pu8Temp)
		{
			cas_capmt_body_remove_descriptor(p_capmt_databuf, s_capmt_len, pu8Temp, &capmtLen);
			for(i=0;i<CAS_SESSION_COUNT;i++)
			{
				if(s_cas_tab_session[i].m_isUsed)
				{
					ssnb=s_cas_tab_session[i].m_ssnb;
					SM_TRACE_INFO(TRMOD_CAS,"cas_stop_discrambling() - send capmt, ssnb: %d\r\n", ssnb);
					send_app_apdu_to_proxy(ssnb, CI_APDU_TAG_CA_PMT, pu8Temp, capmtLen); 
				}
				else
				{
					SM_TRACE_NOTIFY(TRMOD_CAS,"cas_stop_discrambling() - found cas session not open\r\n");
				}
			}
			ITC_DynFree(pu8Temp);
		}
		else
		{
			SM_TRACE_ERROR(TRMOD_CAS,"cas_stop_discrambling() - allocate memory failed, size = %d\r\n", s_capmt_len);
		}
	}
	else
	{
		SM_TRACE_NOTIFY(TRMOD_CAS,"cas_stop_discrambling() - have not send capmt\r\n");
	}
	SM_TRACE_RETURN(TRMOD_CAS, "cas_stop_discrambling() - return\r\n");
	return 0;
}

